package JoReg

import "fmt"

const ()

type ParseTable struct {
	yyPushTable [][]int
	yydTable    [][]int
	rowBase     int
	PdaStmt
}

func MakeParseTable(express string) *ParseTable {
	table := &ParseTable{}
	table.rowBase = SD_STMT
	table.yyPushTable = make([][]int, 0)
	table.yydTable = make([][]int, 6)
	table.initYyPushTable()
	table.initYydTable()
	table.lexerPtr = MakeLexer()
	table.lexerPtr.ParseToken(express)
	table.assignPtr = MakeAssign()
	table.pdaStackPtr = MakeStack()
	table.nameStackPtr = MakeStack()
	return table
}

func (this *ParseTable) initYyPushTable() {
	yyp0 := make([]int, 0)
	this.yyPushTable = append(this.yyPushTable, yyp0)
	yyp1 := []int{SD_STMT, SD_SEMI, SD_ACTION_1, SD_EXPR, SD_ACTION_0}
	this.yyPushTable = append(this.yyPushTable, yyp1)
	yyp2 := []int{SD_EXPR_PRIME, SD_TERM}
	this.yyPushTable = append(this.yyPushTable, yyp2)
	yyp3 := []int{SD_EXPR_PRIME, SD_ACTION_2, SD_TERM, SD_ACTION_0, SD_PLUS}
	this.yyPushTable = append(this.yyPushTable, yyp3)
	yyp4 := make([]int, 0)
	this.yyPushTable = append(this.yyPushTable, yyp4)
	yyp5 := []int{SD_TERM_PRIME, SD_FACTOR}
	this.yyPushTable = append(this.yyPushTable, yyp5)
	yyp6 := []int{SD_TERM_PRIME, SD_ACTION_3, SD_FACTOR, SD_ACTION_0, SD_TIMES}
	this.yyPushTable = append(this.yyPushTable, yyp6)
	yyp7 := make([]int, 0)
	this.yyPushTable = append(this.yyPushTable, yyp7)
	yyp8 := []int{SD_ACTION_4, SD_NUM_OR_ID}
	this.yyPushTable = append(this.yyPushTable, yyp8)
	yyp9 := []int{SD_RP, SD_EXPR, SD_LP}
	this.yyPushTable = append(this.yyPushTable, yyp9)
}

func (this *ParseTable) initYydTable() {
	this.yydTable[0] = []int{0, -1, -1, -1, 1, 1, -1}
	this.yydTable[1] = []int{-1, -1, -1, -1, 2, 2, -1}
	this.yydTable[2] = []int{-1, -1, -1, -1, 5, 5, -1}
	this.yydTable[3] = []int{-1, 4, 3, -1, -1, -1, 4}
	this.yydTable[4] = []int{-1, -1, -1, -1, 8, 9, -1}
	this.yydTable[5] = []int{-1, 7, 7, 6, -1, -1, 7}
}

func (this *ParseTable) tableParse() {
	this.pdaStackPtr.Push(SD_STMT)
	for {
		if this.pdaStackPtr.IsEmpty() {
			break
		}
		symbol := this.pdaStackPtr.Pop()
		switch symbol {
		case SD_ACTION_0:
			varName := this.assignPtr.getName()
			this.nameStackPtr.Push(varName)
		case SD_ACTION_1:
			this.nameStackPtr.Pop()
			this.assignPtr.freeName()
		case SD_ACTION_2:
			varName := this.nameStackPtr.Pop()
			upName := this.nameStackPtr.Top()
			fmt.Printf("%s += %s\n", upName, varName)
			this.assignPtr.freeName()
		case SD_ACTION_3:
			varName := this.nameStackPtr.Pop()
			upName := this.nameStackPtr.Top()
			fmt.Printf("%s *= %s\n", upName, varName)
			this.assignPtr.freeName()
		case SD_ACTION_4:
			varName := this.nameStackPtr.Top()
			fmt.Printf("%s = %c\n", varName, this.lexerPtr.curToken().lexeme)
			this.lexerPtr.advance()
		case SD_SEMI:
			if this.lexerPtr.matchToken(SEMI) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("semi parse error \n")
			}
		case SD_LP:
			if this.lexerPtr.matchToken(OPEN_PAREN) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("open paren parse error \n")
			}
		case SD_RP:
			if this.lexerPtr.matchToken(CLOSE_PAREN) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("close paren parse error \n")
			}
		case SD_NUM_OR_ID:
			if !this.lexerPtr.matchToken(NUMBER) {
				fmt.Printf("number parse error \n")
			}
		case SD_TIMES:
			if this.lexerPtr.matchToken(ASTERISK) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("multiple parse error \n")
			}
		case SD_PLUS:
			if this.lexerPtr.matchToken(PLUS) {
				this.lexerPtr.advance()
			} else {
				fmt.Printf("plus parse error \n")
			}
		default:
			colIndex := this.getColIndex()
			if colIndex == SD_EOI {
				break
			}
			yydTableRowIndex := symbol.(int) - this.rowBase
			yyPushTableIndex := this.yydTable[yydTableRowIndex][colIndex]
			if yyPushTableIndex == -1 {
				fmt.Printf("parse error %c\n", this.lexerPtr.curToken().token)
				break
			}
			for _, yyItem := range this.yyPushTable[yyPushTableIndex] {
				this.pdaStackPtr.Push(yyItem)
			}
		}
	}
}

func (this *ParseTable) getColIndex() int {
	token := this.lexerPtr.curToken()
	switch token.token {
	case EOS:
		return SD_EOI
	case OPEN_PAREN:
		return SD_LP
	case CLOSE_PAREN:
		return SD_RP
	case PLUS:
		return SD_PLUS
	case ASTERISK:
		return SD_TIMES
	case SEMI:
		return SD_SEMI
	case NUMBER:
		return SD_NUM_OR_ID
	}
	return SD_EOI
}
